#include <strmif.h>
#include <comdef.h>
#include <uuids.h>
#include "mkvsourcefilter.hpp"
#include "mkvsourceoutpin.hpp"
#include "cmemallocator.hpp"
//#include "mkvparser.hpp"
#include "mkvparserstream.hpp"
#include <vfwmsgs.h>
#include <cassert>
#include <sstream>
#include <iomanip>
#include <process.h>
#ifdef _DEBUG
#include "odbgstream.hpp"
#include "iidstr.hpp"
using std::endl;
using std::dec;
using std::hex;
#endif


namespace MkvSource
{


Outpin::Outpin(
    Filter* pFilter, 
    MkvParser::Stream* pStream) : 
    Pin(pFilter, PINDIR_OUTPUT, pStream->GetId().c_str()),
    m_pStream(pStream),
    m_hThread(0),
    m_hSampleCanBePopulated(0),
    m_hSampleHasBeenPopulated(0),
    m_hEOS(0)
{
    m_hSampleCanBePopulated = CreateEvent(0, 0, 0, 0);
    assert(m_hSampleCanBePopulated);  //TODO
    
    m_hSampleHasBeenPopulated = CreateEvent(0, 0, 0, 0);
    assert(m_hSampleHasBeenPopulated);  //TODO
    
    m_hStop = CreateEvent(0, 0, 0, 0);
    assert(m_hStop);  //TODO
    
    m_hEOS = CreateEvent(0, 0, 0, 0);
    assert(m_hEOS);  //TODO
    
    m_pStream->GetMediaTypes(m_preferred_mtv);
}


Outpin::~Outpin()
{
    assert(m_hThread == 0);
    assert(!bool(m_pAllocator));
    assert(!bool(m_pInputPin));
    assert(!bool(m_pSample));
    
    delete m_pStream;
    
    BOOL b = CloseHandle(m_hSampleHasBeenPopulated);
    assert(b);
    
    b = CloseHandle(m_hSampleCanBePopulated);
    assert(b);
    
    b = CloseHandle(m_hStop);
    assert(b);
    
    b = CloseHandle(m_hEOS);
    assert(b);
}


void Outpin::Init()  //transition from stopped
{
    assert(m_hThread == 0);
    assert(!bool(m_pSample));
    
    if (m_connection == 0)
        return;  //nothing we need to do
        
    assert(bool(m_pAllocator));
    assert(bool(m_pInputPin));
    
    const HRESULT hr = m_pAllocator->Commit();
    assert(SUCCEEDED(hr));  //TODO
    
    StartThread();
}


void Outpin::Final()  //transition to stopped
{
    if (m_connection == 0)
        return;  //nothing was done
        
    assert(bool(m_pAllocator));
    assert(bool(m_pInputPin));
    
    const HRESULT hr = m_pAllocator->Decommit();
    assert(SUCCEEDED(hr));
    
    StopThread();
    
    //Note that the sample is shared between the pin's streaming thread
    //and the filter's worker thread.  We terminate the filter's
    //worker thread first, before terminating the pin's streaming thread.
    //The means it's safe for the streaming thread to release the
    //sample when it terminates (because the only other thread that
    //manipulates the sample has been terminated).  Now that the 
    //streaming thread has been terminated (see above), it is safe
    //for this thread (the FGM) to manipulate the sample.  In fact
    //we could release the sample here (because the only other threads
    //that manipulate the sample have been terminated), but we defer
    //that to the streaming thread, so all we need to do here is verify
    //that the sample has in fact been released.
    
    assert(!bool(m_pSample));
}


void Outpin::StartThread()
{
    assert(m_hThread == 0);

    BOOL b = ResetEvent(m_hStop);
    assert(b);
    
    b = ResetEvent(m_hSampleCanBePopulated);
    assert(b);
    
    b = ResetEvent(m_hSampleHasBeenPopulated);
    assert(b);
    
    b = ResetEvent(m_hEOS);
    assert(b);
    
    const uintptr_t h = _beginthreadex(
                            0,  //security
                            0,  //stack size
                            &Outpin::ThreadProc,
                            this,
                            0,   //run immediately
                            0);  //thread id
                            
    m_hThread = reinterpret_cast<HANDLE>(h);
    assert(m_hThread);
}


void Outpin::StopThread()
{
    if (m_hThread == 0)
        return;
        
    BOOL b = SetEvent(m_hStop);
    assert(b);
    
    const DWORD dw = WaitForSingleObject(m_hThread, INFINITE);
    assert(dw == WAIT_OBJECT_0);
    
    b = CloseHandle(m_hThread);
    assert(b);
    
    m_hThread = 0;
}


HRESULT Outpin::QueryInterface(const IID& iid, void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
    
    if (iid == __uuidof(IUnknown))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IPin))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IMediaSeeking))
        pUnk = static_cast<IMediaSeeking*>(this);
        
    else
    {
#if 0
        wodbgstream os;
        os << "mkvsource::outpin::QI: iid=" << IIDStr(iid) << std::endl;
#endif        
        pUnk = 0;
        return E_NOINTERFACE;
    }
    
    pUnk->AddRef();
    return S_OK;
}


ULONG Outpin::AddRef()
{
    return m_pFilter->AddRef();
}


ULONG Outpin::Release()
{
    return m_pFilter->Release();
}
    
    
HRESULT Outpin::Connect(
    IPin* pin,
    const AM_MEDIA_TYPE* pmt)
{
    if (pin == 0)
        return E_POINTER;
        
    GraphUtil::IMemInputPinPtr pInputPin;

    HRESULT hr = pin->QueryInterface(&pInputPin);
    
    if (hr != S_OK)
        return hr;

    Filter::Lock lock;
    
    hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
        
    assert(m_pFilter->m_file.IsOpen());
    
    if (m_pFilter->m_state != State_Stopped)
        return VFW_E_NOT_STOPPED;
        
    if (m_connection)
        return VFW_E_ALREADY_CONNECTED;
        
    m_connection_mtv.Clear();
    
    if (pmt)
    {
        hr = QueryAccept(pmt);
         
        if (hr != S_OK)
            return VFW_E_TYPE_NOT_ACCEPTED;
            
        hr = pin->ReceiveConnection(this, pmt);
        
        if (FAILED(hr))
            return hr;
            
        m_connection_mtv.Add(*pmt);        
    }
    else
    {
        ULONG i = 0;
        const ULONG j = m_preferred_mtv.Size();
        
        while (i < j)
        {        
            const AM_MEDIA_TYPE& mt = m_preferred_mtv[i];
            
            hr = pin->ReceiveConnection(this, &mt);
        
            if (SUCCEEDED(hr))
                break;
                        
            ++i;
        }
        
        if (i >= j)
            return VFW_E_NO_ACCEPTABLE_TYPES;
            
        const AM_MEDIA_TYPE& mt = m_preferred_mtv[i];
        m_connection_mtv.Add(mt);
    }
    
    GraphUtil::IMemAllocatorPtr pAllocator;
    
    hr = pInputPin->GetAllocator(&pAllocator);
    
    if (FAILED(hr))
    {
        hr = CMemAllocator::CreateInstance(&pAllocator);
        
        if (FAILED(hr))
            return VFW_E_NO_ALLOCATOR;
    }

    assert(bool(pAllocator));
    
    ALLOCATOR_PROPERTIES props, actual;

    props.cBuffers = -1;    //number of buffers
    props.cbBuffer = -1;    //size of each buffer, excluding prefix
    props.cbAlign = -1;     //applies to prefix, too
    props.cbPrefix = -1;    //imediasample::getbuffer does NOT include prefix

    hr = pInputPin->GetAllocatorRequirements(&props);

    m_pStream->UpdateAllocatorProperties(props);
    
    hr = pAllocator->SetProperties(&props, &actual);
    
    if (FAILED(hr))
        return hr;
        
    hr = pInputPin->NotifyAllocator(pAllocator, 0);  //allow writes
    
    if (FAILED(hr) && (hr != E_NOTIMPL))
        return hr;
        
    m_pAllocator = pAllocator;

    m_connection = pin;  //TODO: use com smartptr here
    m_connection->AddRef();
    
    m_pInputPin = pInputPin;
    
    return S_OK;
}


HRESULT Outpin::OnDisconnect()
{
    m_pInputPin = 0;
    m_pAllocator = 0;
    
    return S_OK;
}


HRESULT Outpin::ReceiveConnection( 
    IPin*,
    const AM_MEDIA_TYPE*)
{
    return E_UNEXPECTED;  //for input pins only
}


HRESULT Outpin::QueryAccept(const AM_MEDIA_TYPE* pmt)
{
    return m_pStream->QueryAccept(pmt);
}


HRESULT Outpin::QueryInternalConnections(IPin**, ULONG* pn)
{
    if (pn == 0)
        return E_POINTER;
        
    *pn = 0;
    return S_OK;        
}

        
HRESULT Outpin::EndOfStream()
{
    return E_UNEXPECTED;  //for inpins only
}


HRESULT Outpin::NewSegment(
    REFERENCE_TIME,
    REFERENCE_TIME,
    double)
{
    return E_UNEXPECTED;
}


HRESULT Outpin::BeginFlush()
{
    return E_UNEXPECTED;
}


HRESULT Outpin::EndFlush()
{
    return E_UNEXPECTED;
}


HRESULT Outpin::GetCapabilities(DWORD* pdw)
{
    if (pdw == 0)
        return E_POINTER;
        
    DWORD& dw = *pdw;
    
    dw = AM_SEEKING_CanSeekAbsolute
           | AM_SEEKING_CanSeekForwards
           | AM_SEEKING_CanSeekBackwards
           | AM_SEEKING_CanGetCurrentPos
           | AM_SEEKING_CanGetStopPos
           | AM_SEEKING_CanGetDuration;
           //AM_SEEKING_CanPlayBackwards
           //AM_SEEKING_CanDoSegments
           //AM_SEEKING_Source

    return S_OK;
}


HRESULT Outpin::CheckCapabilities(DWORD* pdw)
{
    if (pdw == 0)
        return E_POINTER;
        
    DWORD& dw = *pdw;

    const DWORD dwRequested = dw;
    
    if (dwRequested == 0)
        return E_INVALIDARG;
    
    DWORD dwActual;

    const HRESULT hr = GetCapabilities(&dwActual);
    assert(SUCCEEDED(hr)); hr;
    assert(dw);
    
    dw &= dwActual;
    
    if (dw == 0)
        return E_FAIL;
        
    return (dw == dwRequested) ? S_OK : S_FALSE;
}


HRESULT Outpin::IsFormatSupported(const GUID* p)
{
    if (p == 0)
        return E_POINTER;
        
    const GUID& fmt = *p;
        
    if (fmt == TIME_FORMAT_MEDIA_TIME)
        return S_OK;

    //TODO
    //if (fmt != TIME_FORMAT_FRAME)
    //    return S_FALSE;
    
    return S_FALSE;
}


HRESULT Outpin::QueryPreferredFormat(GUID* p)
{
    if (p == 0)
        return E_POINTER;
        
    *p = TIME_FORMAT_MEDIA_TIME;
    return S_OK;
}


HRESULT Outpin::GetTimeFormat(GUID* p)
{
    if (p == 0)
        return E_POINTER;
        
    *p = TIME_FORMAT_MEDIA_TIME;
    return S_OK;
}


HRESULT Outpin::IsUsingTimeFormat(const GUID* p)
{
    if (p == 0)
        return E_INVALIDARG;
        
    const GUID& g = *p;
        
    if (g == TIME_FORMAT_MEDIA_TIME)
        return S_OK;
        
    return S_FALSE;
}


HRESULT Outpin::SetTimeFormat(const GUID* p)
{
    if (p == 0)
        return E_INVALIDARG;
        
    const GUID& g = *p;
    
    if (g == TIME_FORMAT_MEDIA_TIME)
        return S_OK;
        
    return E_INVALIDARG;
}
    

HRESULT Outpin::GetDuration(LONGLONG* p)
{
    if (p == 0)
        return E_POINTER;
        
    Filter::Lock lock;
    
    const HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;

    LONGLONG& d = *p;        
    d = m_pStream->GetDuration();
    
    return S_OK;
}


HRESULT Outpin::GetStopPosition(LONGLONG* p)
{
    if (p == 0)
        return E_POINTER;
        
    Filter::Lock lock;
    
    const HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;

    LONGLONG& pos = *p;
    pos = m_pStream->GetStopPosition();
    
    return S_OK;
}


HRESULT Outpin::GetCurrentPosition(LONGLONG* p)
{
    if (p == 0)
        return E_POINTER;
        
    Filter::Lock lock;
    
    const HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;

    LONGLONG& pos = *p;
    pos = m_pStream->GetCurrPosition();
    
    return S_OK;
}


HRESULT Outpin::ConvertTimeFormat( 
    LONGLONG* ptgt,
    const GUID* ptgtfmt,
    LONGLONG src,
    const GUID* psrcfmt)
{
    if (ptgt == 0)
        return E_POINTER;
        
    LONGLONG& tgt = *ptgt;
        
    const GUID& tgtfmt = ptgtfmt ? *ptgtfmt : TIME_FORMAT_MEDIA_TIME;
    const GUID& srcfmt = psrcfmt ? *psrcfmt : TIME_FORMAT_MEDIA_TIME;
    
    if (tgtfmt != TIME_FORMAT_MEDIA_TIME)
        return E_INVALIDARG;
        
    if (srcfmt != TIME_FORMAT_MEDIA_TIME)
        return E_INVALIDARG;
        
    if (src < 0)
        return E_INVALIDARG;
            
    tgt = src;
    return S_OK;                
}


HRESULT Outpin::SetPositions( 
    LONGLONG* pCurr,
    DWORD dwCurr_,
    LONGLONG* pStop,
    DWORD dwStop_)
{
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
        
#ifdef _DEBUG
    odbgstream os;
    os << "Outpin::SetPos(begin): pCurr=" 
       << dec << (pCurr ? *pCurr : -1)
       << " dwCurr=0x"
       << hex << dwCurr_
       << " pStop="
       << dec << (pStop ? *pStop : -1)
       << " dwStop=0x"
       << hex << dwStop_
       << endl;
#endif

    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;

    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    const DWORD dwStopPos = dwStop_ & AM_SEEKING_PositioningBitsMask;
    
    if (dwCurrPos == AM_SEEKING_NoPositioning)
    {
        if (dwCurr_ & AM_SEEKING_ReturnTime)
        {
            if (pCurr == 0)
                return E_POINTER;
                
            *pCurr = m_pStream->GetCurrTime();
        }
        
        if (dwStopPos == AM_SEEKING_NoPositioning)
        {
            if (dwStop_ & AM_SEEKING_ReturnTime)
            {
                if (pStop == 0)
                    return E_POINTER;
                    
                *pStop = m_pStream->GetStopTime();
            }
            
            return S_FALSE;  //no position change
        }

        if (pStop == 0)
            return E_INVALIDARG;
            
        LONGLONG& tStop = *pStop;
        
        //It makes sense to be able to adjust this during stop.
        //However, if we're paused/running, then the thread is either
        //still sending frames, or it has already sent EOS.  In the 
        //former case, it makes sense to be able to adjust where
        //the running thread will stop.  But in the latter case, 
        //the thread has already terminated, and it wouldn't 
        //make any sense to restart the thread because there
        //would be a large time gap.
        
        m_pStream->SetStopPosition(tStop, dwStop_);
        
        if (dwStop_ & AM_SEEKING_ReturnTime)
            tStop = m_pStream->GetStopTime();

        //TODO: You're supposed to return S_FALSE if there has
        //been no change in position.  Does changing only the stop
        //position count has having changed the position?
                
        return S_OK;
    }
    
    //Check for errors first, before changing any state.
    
    if (pCurr == 0)
        return E_INVALIDARG;
        
    switch (dwCurrPos)
    {
        case AM_SEEKING_IncrementalPositioning:
        default:
            return E_INVALIDARG;  //applies only to stop pos

        case AM_SEEKING_AbsolutePositioning:
            break;

        case AM_SEEKING_RelativePositioning:
        {
            //TODO:
            //All pins must have a common base time, otherwise the 
            //streams will be out of sync.  Since each stream has
            //a different current time (frames don't align exactly
            //across streams), each base time would be different,
            //hence we must disallow relative positions when we're
            //attempting to seek on multiple streams.
            //
            //One possibility is to have a distinguished pin whose
            //time we use as the reference time.
            
            const int n = m_pFilter->GetConnectionCount();
            
            if (n > 1)
                return E_NOTIMPL;  //
    
            break;
        }      
    }

    if (dwStopPos == AM_SEEKING_NoPositioning)
    {
        if (((dwStop_ & AM_SEEKING_ReturnTime) != 0) && (pStop == 0))
            return E_POINTER;        
    }
    else if (pStop == 0)
        return E_INVALIDARG;        

    //TODO: this check is incorrect.  We have to allow a seek 
    //to happen no matter what the filter state is.  We have to 
    //something like this:
    // (1) m_connection->BeginFlush() and set hStop
    // (2) release filter lock and wait for thread to terminate
    // (3) seize filter lock and set posn(s)
    // (4) restart thread
    // (5) release filter lock
    //
    //Note that the streaming thread doesn't ever acquire the 
    //filter lock -- that is only the case for the worker thread.
    //We just need to kill the streaming thread.  If it's blocked
    //on GetBuffer than BeginFlush will release samples.  If it's
    //blocked on Receive then Flush will reject receipt of sample.
    //So eventually the thread will terminate.  We can't hold
    //the filter lock while we're waiting for thread to terminate.
    //It's OK to release the filter lock, because it's the 
    //FGM thread that is the caller.  The only thing we have to 
    //worry about is the action of the worker thread.
    //
    //Stream::SetCurr(Stop)Position doesn't change the state
    //of what we have cached.  It's only the worker thread that
    //changes cache state (by loading clusters).  The problem occurs
    //when this sequence happens:
    //  (1) satisfy this set position, calculating base time
    //  (2) worker thread satisfies populate request from another
    //      running streaming thread, changing cache state
    //  (3) seek happens on next pin, but base time is different
    //      from the value calculated for this pin.
    //
    //Assuming this analysis is correct, it would be nice to kill
    //all of the streaming threads, seek all of the pins, and then
    //restart the pins.  Maybe what we can do is:
    //  (1) first pin to receive seek request kills all streaming threads
    //  (2) set position on all pins
    //  (3) restart streaming thread for this pin
    //  (4) as other pins are told to seek, they notice that a seek
    //      as occurred and re-starts its own streaming thread
    //
    //Note that is the filter state is stopped, there's nothing special
    //we need to do.  We're only figuring what to do when the filter
    //state is paused/running, when there are active streaming threads.
    //
    //How does a pin know whether it has been seeked?  If filter 
    //state is paused/running, and there's no streaming thread active,
    //it would simply restart the thread.  The problem is that
    //a thread can be terminated for other reasons, such as having
    //reached end-of-stream.  So if the first pin to be seeked happens
    //to have its thread already terminated, we would falsely assume
    //that it had already been seeked and all we need to is restart
    //its streaming thread.
    //
    //The problem is that we want to guarantee that all pins have
    //the same base time.  We have been assuming that if the worker
    //thread manipulated the file after we release the filter lock
    //here, then that would change the base time.  The problem is
    //when Segment:::GetCluster is called -- whether or not it
    //returns NULL depends on how much is loaded.  Maybe we could
    //decide that:
    //  if segment->unparsed <= 0 then
    //     worker thread can't change cache state (because we're fully loaded)
    //     go ahead and return NULL when seek time >= duration
    //  else
    //     Never return NULL.  If time is large relative to existing cache,
    //     return value of last cluster loaded.
    //  end if
    //
    //In that case, there's nothing special we would need to do wrt
    //the threads.
    //
    //Another possibility is to have Segement::GetCluster return
    //a pseudo-cluster value (non-NULL) when the time is large relative
    //to the cache (or to the duration when fully loaded).  This 
    //nonce-value would always mean "beyond and of file".
    //
    //The important point is that all pins get this same cluster
    //value, no matter what the worker thread might have done in between
    //seeks on different pins.  The same cluster vlaue means the
    //same base time, which is necessary to keep all the streams in sync.
    //
    //We want to be able to skip ahead in the file during a seek,
    //so clusters are able to be load out-of-order, but maybe the
    //solution is to just navigate among the clusters to find
    //their location and size, but don't actually load the frames.
    //That would mean there's nothiing special we needs to do wrt
    //find a cluster that corresponds to a time.  But to get the time
    //for a cluster, we can't assume that it's the first sub-element
    //of the cluster.  But we could have a roaming posn ptr for
    //each cluster, as we do for the segment, to allow us to 
    //determine how much of the cluster has been parsed.  We
    //could stop loading as soon as we find the cluster's time
    //(only is pathological cases would the time be not right up
    //at the front of the cluster anyway).
    //
    //But then again, none of this will work in the splitter case
    //anway, and we can't do any pre-loading anyway (we don't even
    //have access to the seekhead, which is at the end of the file).
    //The problem we're having with clusters (attempting to seek
    //to a time beyond what's actually loaded) is that we're allowing
    //large times, and GetCluster returns 0 -- but that can change
    //if more clusters get loaded.  If we throttle the requested seek
    //time (that's what IMediaSeeking::Available is supposed to do), 
    //then returning NULL can't happen.  But then does this just
    //defer the problem, from Segment::GetCluster to 
    //IMediaSeeking::Available?
    //
    //But then again, this is the source filter case, not the splitter
    //case, so we should be able to seek.  We could bring back load:
    //  pSegment->Load(time)
    //which would load clusters until it loads a cluster whose time
    //is t >= time.  That would guarantee that Segment::GetCluster
    //returns non-NULL, and it would also mean that there's nothing
    //the worker thread could do to change cache state in a harmful
    //way (because that cluster has already been load, so if the worker
    //thread happens to load another than it wouldn't matter, since 
    //that wouldn't affect the result of Segment::GetCluster).
    //
    //Actually, a cluster wouldn't really need its own roaming
    //posn ptr.  If the cluster has a non-zero size, and its block
    //group array is empty, that means we skipped loading the blocks
    //during our initial pass (e.g. a seek) and so we can load the
    //blocks on the fly when GetNextBlock is called.
    
    assert(pCurr);  //vetted above
    LONGLONG& tCurr = *pCurr;

    //m_pStream->LoadCurrPosition(tCurr, dwCurr_);
    
    //We have loaded the cluster that contains time tCurr.  This
    //means the worker thread cannot influence the result of future 
    //calls to Segment::GetCluster.      

    if (m_pFilter->m_state != State_Stopped)
    {
        lock.Release();

        //The streaming thread and worker thread share the media sample, so
        //we must be careful about how we terminate the streaming thread,
        //because it releases the media sample before it exits.  So we 
        //just allow the streaming thread to do business as usual (which 
        //means that perhaps a sample gets pushed downstream).  By flushing,
        //the streaming thread will eventually attempt to send a sample
        //downstream that gets rejected, which causes the streaming thread
        //to exit automatically.  In particular, this means we can NOT set
        //the hStop event, because use of that event assumes that the
        //worker thread has already been terminated, which is not the
        //case here.
        
        hr = m_connection->BeginFlush();

        assert(m_hThread);        

        const DWORD dw = WaitForSingleObject(m_hThread, INFINITE);
        assert(dw == WAIT_OBJECT_0);
        
        const BOOL b = CloseHandle(m_hThread);
        assert(b);
        
        m_hThread = 0;
        
        hr = m_connection->EndFlush();

        hr = lock.Seize(m_pFilter);
        assert(SUCCEEDED(hr));  //TODO
    }
    
        
    if (dwStopPos == AM_SEEKING_NoPositioning)
    {
        m_pStream->SetCurrPosition(tCurr, dwCurr_);
        
        //TODO: I still haven't figured what should happen to the 
        //stop position if the user doesn't seek the stop time
        //too.  For now I assume that that user wants to play
        //the entire remainder of the stream starting from the 
        //seek time.
        
        m_pStream->SetStopPositionEOS();
    }
    else
    {
        assert(pStop);  //vetted above

        m_pStream->SetCurrPosition(tCurr, dwCurr_);                
        m_pStream->SetStopPosition(*pStop, dwStop_);
    }
    
    if (dwCurr_ & AM_SEEKING_ReturnTime)
        tCurr = m_pStream->GetCurrTime();

    if (dwStop_ & AM_SEEKING_ReturnTime)
    {
        assert(pStop);  //we checked this above        
        *pStop = m_pStream->GetStopTime();
    }
    
    if (m_pFilter->m_state != State_Stopped)
        StartThread();

#ifdef _DEBUG
    os << "Outpin::SetPos(end): pCurr=" 
       << dec << (pCurr ? *pCurr : -1)
       << " pStop="
       << dec << (pStop ? *pStop : -1)
       << endl;
#endif

    return S_OK;
}


HRESULT Outpin::GetPositions( 
    LONGLONG* pCurrPos,
    LONGLONG* pStopPos)
{
    Filter::Lock lock;
    
    const HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
        
    if (pCurrPos)
        *pCurrPos = m_pStream->GetCurrPosition();
        
    if (pStopPos)
        *pStopPos = m_pStream->GetStopPosition();

    return S_OK;
}


HRESULT Outpin::GetAvailable( 
    LONGLONG* pEarliest,
    LONGLONG* pLatest)
{
    if (pEarliest)
        *pEarliest = 0;
        
    return GetDuration(pLatest);
}



HRESULT Outpin::SetRate(double r)
{
    if (r == 1)
        return S_OK;
        
    if (r <= 0)
        return E_INVALIDARG;
        
    return E_NOTIMPL;  //TODO: better return here?
}


HRESULT Outpin::GetRate(double* p)
{
    if (p == 0)
        return E_POINTER;
        
    *p = 1;
    return S_OK;
}


HRESULT Outpin::GetPreroll(LONGLONG* p)
{
    if (p == 0)
        return E_POINTER;
        
    *p = 0;
    return S_OK;
}


HRESULT Outpin::GetName(PIN_INFO& i) const
{
    const std::wstring name = m_pStream->GetName();

    const size_t buflen = sizeof(i.achName)/sizeof(WCHAR);
    
    const errno_t e = wcscpy_s(i.achName, buflen, name.c_str());
    e;
    assert(e == 0);
    
    return S_OK;
}


unsigned Outpin::ThreadProc(void* pv)
{
    Outpin* const pPin = static_cast<Outpin*>(pv);
    assert(pPin);
    
    return pPin->Main();
}
    
unsigned Outpin::Main()
{
    assert(m_hSampleCanBePopulated);
    assert(m_hSampleHasBeenPopulated);
    assert(bool(m_pAllocator));
    assert(m_connection);
    assert(bool(m_pInputPin));
    
    const HANDLE hh[] =
    {
        m_hStop, 
        m_hSampleHasBeenPopulated,
        m_hEOS
    };
    
    //TODO: we need duration to send NewSegment
    //HRESULT hr = m_connection->NewSegment(st, sp, 1);
    
    for (;;)
    {
        assert(!bool(m_pSample));
        
        HRESULT hr = m_pAllocator->GetBuffer(&m_pSample, 0, 0, 0);
        
        if (hr != S_OK)
            return 0;
            
        assert(bool(m_pSample));
        
        const BOOL b = SetEvent(m_hSampleCanBePopulated);
        assert(b);

        const DWORD dw = WaitForMultipleObjects(3, hh, 0, INFINITE);
                            
        if (dw == WAIT_OBJECT_0)  //hStop
        {
            //Note that the filter's worker thread is terminated
            //before this streaming thread is terminated, so there's
            //no synchronization problem when releasing the sample.

            m_pSample = 0;
            return 0;            
        }
            
        if (dw == (WAIT_OBJECT_0 + 2))  //hEOS
        {            
            hr = m_connection->EndOfStream();
            m_pSample = 0;
            return 0;
        }
        
        assert(dw == (WAIT_OBJECT_0 + 1));  //hSampleHasBeenPopulated

        hr = m_pInputPin->Receive(m_pSample);
        //TODO: here's how we can kill the threads during a seek.
        //While we're flushing, the downstream filter will reject
        //receipt of samples, and so this kills the streaming thread.
        //When they're all dead, we end the flush, do the seek, and
        //the re-start the threads.
        //
        //But of course, this only kills the thread for this stream.
        //We could set a filter-wide stop flag. (Actually, the hStop
        //flag is per-pin, but we could probably make that filter-wide
        //with manual-reset.)  
        //
        //Since the FGM calls all streams, each outpin is going to 
        //set the stop flag.  If a pin detects that there are extant
        //streaming threads, then it could just exit immediately.
        //Or maybe it would wait for its own streaming thread to
        //finish, and then do the seek on its behalf.  That would
        //allow it to return values (e.g. when AM_SEEKING_ReturnTime).
        //Can it re-start the thread right away?  If not, the 
        //outpin that detects that all other threads have been 
        //terminated can re-start all of them.
        
        m_pSample = 0;
        
        if (hr != S_OK)
            return 0;
    }
}


void Outpin::PopulateSample()
{
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    assert(SUCCEEDED(hr));  //TODO
    
    assert(bool(m_pSample));
    
    //TODO: handle incremental loading
    hr = m_pStream->PopulateSample(m_pSample);  
    
    if (hr == S_OK)
    {
        const BOOL b = SetEvent(m_hSampleHasBeenPopulated);
        assert(b);
    }
    else
    {
        assert(hr == S_FALSE);
        
        const BOOL b = SetEvent(m_hEOS);
        assert(b);
    }
}


} //end namespace MkvSource

